/**
@module contract-watcher.js
@desc This module provides an API for sending and receiving Whisper messages
@author Westlad
*/

import utils from 'zkp-utils';
import Web3 from './web3';

const web3 = Web3.connection();
const TRANSFER_TOPIC = '0xeca7945f';

/**
function to generate Whisper keys and return them
@param {object} id - the 'identity' of the Whisper user.  Contains addresses, names, key material
as a minimum it must contain an Etherium address and a name string
@returns {object} the users identity, with the Whisper key-pair added
*/
async function generateWhisperKeys(id) {
  if (!web3.utils.isAddress(id.address))
    throw new Error('no valid Ethereum Address has been set for this party');
  return web3.shh.newKeyPair(); // generate a new shh keyPair
}

/**
Function to return the Whisper public key, given an shhIdentity generated by the
generateWhisperKeys function above
@param {object} id - the 'identity' of the Whisper user.  Contains addresses, names, key material
as a minimum it must contain the Whisper shhIdenity
*/
async function getWhisperPublicKey(id) {
  return web3.shh.getPublicKey(id.shhIdentity);
}

/**
function to subscribe to whisper messages.  You need a Whisper key pair for this to work
@param {object} idReceiver - the 'identity' of the Whisper user.  Contains addresses, names, key material.
As a minimum it must contain the Whisper key pair.
@param {string} topic - the topic to subscribe to
@param {function} listener - callback that will be called when a topical message is received
This version returns the raw hex Whisper payload
*/
async function subscribe(idReceiver, topic = TRANSFER_TOPIC, listener) {
  if (utils.strip0x(topic).length !== 8) throw new Error('Whisper topic must be 4 bytes long');
  if (idReceiver.shhIdentity === undefined)
    throw new Error(
      'no valid Whisper key pair was found.  Please generate these before subscribing',
    );
  const subscription = await web3.shh.subscribe('messages', {
    // subscribe to transfer messages to me
    privateKeyID: idReceiver.shhIdentity,
    topics: [topic], // this identifies the topic
  });
  subscription.on('data', listener);
  return subscription;
}

/**
Function to decode a javascript object encoded as a Whisper hex message payload string
@param {object} msgHex - a hex string encoding the javascript object
@returns {string} - the decoded javascript object
*/
function decodeMessage(msgHex) {
  const msgStr = utils.hexToUtf8String(msgHex);
  return JSON.parse(msgStr);
}

/**
function to subscribe to whisper messages.  You need a Whisper key pair for this to work.
This function expects messages which encode a javascript object and will attempt to
decode them, returning the original object
@param {object} idReceiver - the 'identity' of the Whisper user.  Contains addresses, names, key material.
As a minimum it must contain the Whisper key pair.
@param {string} topic - the topic to subscribe to
@param {object} userData - holds user's JWT token to enable calls through the API gateway
on the user's behalf
@param {function} listener - callback that will be called when a topical message is received
This version will return a Javascript object as the payload (assuming sendObject was used to send
the object)
*/
async function subscribeObject(idReceiver, topic = TRANSFER_TOPIC, userData, listener) {
  if (utils.strip0x(topic).length !== 8) throw new Error('Whisper topic must be 4 bytes long');
  if (idReceiver.shhIdentity === undefined)
    throw new Error(
      'no valid Whisper key pair was found.  Please generate these before subscribing',
    );
  const subscription = await web3.shh.subscribe('messages', {
    // subscribe to transfer messages to me
    privateKeyID: idReceiver.shhIdentity,
    topics: [topic], // this identifies the topic
  });
  subscription.on('data', msg => {
    const decodedMsg = { ...msg, payload: decodeMessage(msg.payload) };
    listener(decodedMsg, userData);
  });
  return subscription;
}

async function unsubscribe() {
  web3.shh.clearSubscriptions();
}

/**
function to send a Whisper message
@param {string} name - the name of the receipient (used to look up their Whisper public key)
@param {string} message - the message to be sent
@param {object} idSender - the 'identity' of the sender (used to extract Whisper private key to sign the message)
@param {bytes4} topic - the topic to post to (four bytes)
@param {string} pkReceiver - the receipient's public key
*/
async function sendMessage(message, idSender, pkReceiver, topic = TRANSFER_TOPIC) {
  if (utils.strip0x(topic).length !== 8) throw new Error('Whisper topic must be 4 bytes long');
  if (idSender.shhIdentity === undefined)
    throw new Error('Whisper identity not found in id object');
  try {
    web3.shh.post({
      pubKey: pkReceiver, // encrypts using the receiver's public key
      sig: idSender.shhIdentity, // signs the message using the keyPair ID
      ttl: 10,
      topic,
      payload: message,
      powTime: 3,
      powTarget: 0.5,
    });
  } catch (err) {
    console.error('Error from message post:', err);
  }
}

/**
Function to encode a javascript object as a Whisper hex message payload string
@param {object} msgObj - the object to encode
@returns {string} - a hex string encoding the javascript object
*/
function encodeMessage(msgObj) {
  const msgStr = JSON.stringify(msgObj);
  const buf = Buffer.from(msgStr, 'utf8');
  const msgHex = buf.toString('hex');
  return utils.ensure0x(msgHex);
}

/**
function to send a Whisper message, containing an encoded javascript object.
This function will do the encoding Note that
the message is delayed for 3 seconds.  This is for rare occassions when a user sends
messages to themselves.  It allows time for other code to transistion from 'transmit'
to 'receive'.  It's a little crude and 3s is overkill but will do for now.
@param {string} name - the name of the receipient (used to look up their Whisper public key)
@param {string} message - the javascript object to be sent
@param {object} idSender - the 'identity' of the sender (used to extract Whisper private key to sign the message)
@param {bytes4} topic - the topic to post to (four bytes)
@param {string} pkReceiver - the receipient's public key
*/
async function sendObject(message, idSender, pkReceiver, topic = TRANSFER_TOPIC) {
  if (utils.strip0x(topic).length !== 8) throw new Error('Whisper topic must be 4 bytes long');
  if (idSender.shhIdentity === undefined)
    throw new Error('Whisper identity not found in id object');
  try {
    setTimeout(async () => {
      web3.shh.post({
        pubKey: pkReceiver, // encrypts using the receiver's public key
        sig: idSender.shhIdentity, // signs the message using the keyPair ID
        ttl: 10,
        topic,
        payload: encodeMessage(message),
        powTime: 3,
        powTarget: 0.5,
      });
    }, 3000); // a short delay in case the sender and receiver are the same person
    // so that there is time for the application to go from transmit to receive.
  } catch (err) {
    console.error('Error from message post:', err);
  }
}

export default {
  sendMessage,
  generateWhisperKeys,
  subscribe,
  getWhisperPublicKey,
  unsubscribe,
  encodeMessage,
  decodeMessage,
  sendObject,
  subscribeObject,
};
